/* -LICENSE-START-
** Copyright (c) 2013 Blackmagic Design
**
** Permission is hereby granted, free of charge, to any person or organization
** obtaining a copy of the software and accompanying documentation covered by
** this license (the "Software") to use, reproduce, display, distribute,
** execute, and transmit the Software, and to prepare derivative works of the
** Software, and to permit third-parties to whom the Software is furnished to
** do so, all subject to the following:
** 
** The copyright notices in the Software and this entire statement, including
** the above license grant, this restriction and the following disclaimer,
** must be included in all copies of the Software, in whole or in part, and
** all derivative works of the Software, unless such copies or derivative
** works are solely in the form of machine-executable object code generated by
** a source language processor.
** 
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
** DEALINGS IN THE SOFTWARE.
** -LICENSE-END-
*/

#pragma once

#if defined(INCLUDE_BLACKMAGIC)
#include <Windows.h>
#include <vector>
#include "DeckLinkAPI_h.h"
#include "DirectXHelper.h"
#include "BufferedTextureFetch.h"

class DeckLinkDevice : public IDeckLinkInputCallback
{
private:

    enum PixelFormat
    {
        YUV,
        BGRA
    };

    PixelFormat pixelFormat = PixelFormat::YUV;

    ULONG                     m_refCount;
    IDeckLink*                m_deckLink;
    IDeckLinkInput*           m_deckLinkInput;
    IDeckLinkOutput*          m_deckLinkOutput;
    BOOL                      m_supportsFormatDetection;
    bool                      m_currentlyCapturing;
    CRITICAL_SECTION          m_captureCardCriticalSection;
    CRITICAL_SECTION          m_outputCriticalSection;

    BYTE* localFrameBuffer;
    BYTE* rawBuffer =           new BYTE[FRAME_BUFSIZE_YUV];

    BYTE* stagingBuffer =       new BYTE[FRAME_BUFSIZE_RGBA];
    BYTE* outputBuffer =        new BYTE[FRAME_BUFSIZE_RGBA];
    BYTE* outputBufferRaw =     new BYTE[FRAME_BUFSIZE_YUV];

    BMDTimeValue frameDuration = 0;

    class BufferCache
    {
    public:
        BYTE* buffer;
        LONGLONG timeStamp;
        int pixelChange;

        void ComputePixelChange(BYTE* prevBuffer, BMDPixelFormat framePixelFormat);
    };

    #define MAX_NUM_CACHED_BUFFERS 20
    BufferCache bufferCache[MAX_NUM_CACHED_BUFFERS];
    int captureFrameIndex;

    BufferCache& GetOldestBuffer();

    bool dirtyFrame = true;

    ID3D11ShaderResourceView* _colorSRV;
    ID3D11Texture2D* _outputTexture;
    ID3D11Device* device;

    bool _useCPU;
    bool _passthroughOutput;
    BufferedTextureFetch outputTextureBuffer;

    void SetupVideoOutputFrame(BMDDisplayMode videoDisplayMode);
    int lastCompositorFrameIndex = -1;

public:
    DeckLinkDevice(IDeckLink* device);
    virtual ~DeckLinkDevice();

    bool                                Init(ID3D11ShaderResourceView* colorSRV, ID3D11Texture2D* outputTexture, bool useCPU = false, bool passthroughOutput = false);
    bool                                IsCapturing() { return m_currentlyCapturing; };
    bool                                SupportsFormatDetection() { return (m_supportsFormatDetection == TRUE); };
    bool                                StartCapture(BMDDisplayMode videoDisplayMode);
    void                                StopCapture();
    IDeckLink*                          DeckLinkInstance() { return m_deckLink; }

    void Update(int compositeFrameIndex);

    bool supportsOutput = true;

    // IUnknown interface
    virtual HRESULT  STDMETHODCALLTYPE    QueryInterface (REFIID iid, LPVOID *ppv);
    virtual ULONG    STDMETHODCALLTYPE    AddRef ();
    virtual ULONG    STDMETHODCALLTYPE    Release ();

    // IDeckLinkInputCallback interface
    virtual HRESULT  STDMETHODCALLTYPE    VideoInputFormatChanged (/* in */ BMDVideoInputFormatChangedEvents notificationEvents, /* in */ IDeckLinkDisplayMode *newDisplayMode, /* in */ BMDDetectedVideoInputFormatFlags detectedSignalFlags);
    virtual HRESULT  STDMETHODCALLTYPE    VideoInputFrameArrived (/* in */ IDeckLinkVideoInputFrame* frame, /* in */ IDeckLinkAudioInputPacket* audioPacket);

    LONGLONG GetTimestamp(int frame)
    {
        return bufferCache[frame % MAX_NUM_CACHED_BUFFERS].timeStamp;
    }

    LONGLONG GetDurationHNS()
    {
        return frameDuration;
    }

    int GetCaptureFrameIndex()
    {
        return captureFrameIndex;
    }

    int GetPixelChange(int frame)
    {
        return bufferCache[frame % MAX_NUM_CACHED_BUFFERS].pixelChange;
    }

    int GetNumQueuedOutputFrames();

    bool OutputYUV();
};

class DeckLinkDeviceDiscovery :  public IDeckLinkDeviceNotificationCallback
{
private:
    IDeckLinkDiscovery*                 m_deckLinkDiscovery;
    ULONG                               m_refCount;
    IDeckLink*                          m_deckLink = nullptr;

public:
    DeckLinkDeviceDiscovery();
    virtual ~DeckLinkDeviceDiscovery();

    IDeckLink*                          GetDeckLink() { return m_deckLink; }

    bool                                Enable();
    void                                Disable();

    // IDeckLinkDeviceNotificationCallback interface
    virtual HRESULT  STDMETHODCALLTYPE    DeckLinkDeviceArrived (/* in */ IDeckLink* deckLink);
    virtual HRESULT  STDMETHODCALLTYPE    DeckLinkDeviceRemoved (/* in */ IDeckLink* deckLink);

    virtual HRESULT  STDMETHODCALLTYPE    QueryInterface (REFIID iid, LPVOID *ppv);
    virtual ULONG    STDMETHODCALLTYPE    AddRef ();
    virtual ULONG    STDMETHODCALLTYPE    Release ();
};
#endif
